package org.fujene.memoryblock.invert;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.fujene.communicate.indexer.term.TermPackage;
import org.fujene.constants.Constants;
import org.fujene.memoryblock.MemoryBlock;
import org.fujene.structure.LookupPointer;
import org.fujene.toolbox.ByteConverter;

public class InvertIndexBlock extends MemoryBlock{
	public static final int OPT_LEN = 0;
	public static final int OPT_PERTERM = 1;
	public static final int OPT_BOOLFLAG = 2;
	public static final int OPT_MULFLAG = 3;
	public static final int OPT_BOOLARR = 4;
	public static final int OPT_UNIQUE = 5;
	public static final int OPTSIZE = 6;

	public InvertIndexBlock(ByteBuffer block, int bodySize, int ID, int repBlock){
		super(block, bodySize, ID, Constants.MEMORY_TYPE_INVINDEX, repBlock);
	}

	public InvertIndexBlock(ByteBuffer existingBody){
		super(existingBody);
	}

	public InvertIndexPackage read(int ptr){
		int perID = option[OPT_BOOLARR] + (option[OPT_MULFLAG] * INT) + LONG;
		int newest = getInt(ptr, NEWEST); // The maximum value to be written
		ptr += NEWEST;
		int lastPtr = ptr + (option[OPT_PERTERM] * perID) - LONG;
		// check whether it reads all or not
		int readLength = option[OPT_PERTERM];
		if(getLong(lastPtr, LONG) == 0L) readLength = newest;
		InvertIndexPackage pkg = InvertIndexPackage.setPackage(readLength, getID(), ptr);
		pkg.newest(newest);

		for(int i = 0; i < readLength; i++){
			boolean[] binaryFlags = TermPackage.byteToFlag(read(ptr, option[OPT_BOOLARR]));
			ptr += option[OPT_BOOLARR];
			pkg.binaryFlag(binaryFlags, i);

			int[] multiFlags = new int[option[OPT_MULFLAG]];
			for(int j = 0; j < option[OPT_MULFLAG]; j++){
				multiFlags[j] = getInt(ptr, INT);
				ptr += INT;
			}
			pkg.multiFlag(multiFlags, i);

			long val = getLong(ptr, LONG);
			pkg.contentPtr(val, i);
			ptr += LONG;
		}

		return pkg;
	}

	private boolean renumber(int ptr, long ID, int[] keys){
		if(keys.length == 0) return false;
		ptr += NEWEST;
		for(int i = 0; i < option[OPT_PERTERM]; i++){
			ptr += option[OPT_BOOLARR] + (INT * option[OPT_UNIQUE]);
			int comp = getInt(ptr, INT);
			ptr += INT * (option[OPT_MULFLAG] - option[OPT_UNIQUE]);
			if(keys[option[OPT_UNIQUE]] == comp){
				setLong(ptr, LONG, ID);
				return true;
			}
			ptr += LONG;
		}

		return false;
	}

	public LookupPointer write(int ptr, long ID, boolean[] binaryFlag, int[] multiFlag){
		int perID = option[OPT_BOOLARR] + (option[OPT_MULFLAG] * INT) + LONG;
		int newest = getInt(ptr, NEWEST);
		LookupPointer stored = LookupPointer.valueOf(getID(), ptr);
		newest = (newest == option[OPT_PERTERM] - 1? 0: newest + 1);
		setInt(ptr, NEWEST, newest);

		if(!renumber(ptr, ID, multiFlag)){
			ptr += NEWEST + ((newest == 0? option[OPT_PERTERM] - 1: newest - 1) * perID);
			stored = LookupPointer.valueOf(getID(), ptr);
			store(TermPackage.flagToByte(binaryFlag), ptr);
			ptr += option[OPT_BOOLARR];
			for(int i = 0; i < option[OPT_MULFLAG]; i++){
				setInt(ptr, INT, multiFlag[i]);
				ptr += INT;
			}
			setLong(ptr, LONG, ID);
		}

		return stored;
	}

	public void resetPointer(int ptr, LookupPointer newPtr){
		store(newPtr.toBytes(), ptr);
	}

	public synchronized int allocate(){
		int newSize = NEWEST; // newest director
		newSize += (option[OPT_PERTERM] * option[OPT_BOOLARR]); // binary flags
		newSize += (option[OPT_PERTERM] * (option[OPT_MULFLAG] * INT)); // multivalue flags
		newSize += (option[OPT_PERTERM] * LONG); // IDs
		if(bodyVacancy < newSize) return -1;
		bodyVacancy -= newSize;
		option[OPT_LEN]++;
		return bodySize - bodyVacancy - newSize;
	}

	public int length(){
		return NEWEST + (option[OPT_PERTERM] * LONG);
	}

	@Override
	public int getOptionSize(){
		return OPTSIZE;
	}

	@Override
	public byte[] getOptionByteStream(){
		ByteArrayOutputStream stream = new ByteArrayOutputStream();

		for(int i = 0; i < OPTSIZE; i++){
			try{
				stream.write(ByteConverter.intToByte(option[i], 4));
			}catch(IOException e){}
		}

		return stream.toByteArray();
	}

	@Override
	public int getOption(int index){
		if(index >= OPTSIZE) return -1;
		return option[index];
	}

	@Override
	public void setOption(int index, int value){
		if(index == OPT_BOOLARR || index >= OPTSIZE) return;
		option[index] = value;
		if(index == OPT_BOOLFLAG){
			if(value == 0) option[OPT_BOOLARR] = 0;
			else if(value % 8 == 0) option[OPT_BOOLARR] = value / 8 + FLAGLEN;
			else option[OPT_BOOLARR] = value / 8 + FLAGLEN + 1;
		}
	}

	@Override
	public String getClassName(){
		return new String("InvertIndex");
	}
}
